﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;

namespace Loki.Model
{
	/// <summary>
	/// Entity primary key mapper
	/// </summary>
	/// <typeparam name="TEntityClass">Entity class type</typeparam>
	/// <typeparam name="TEntityKey">Entity key type</typeparam>
	public class UniqueKeyMapper<TEntityClass, TEntityKey> : LokiObject, IDisposable, IKeyMapper<TEntityClass>, IEnumerable<TEntityClass>
	{
		#region Constructors
		private ReaderWriterLockSlim _Lock;

		public UniqueKeyMapper(Expression<Func<TEntityClass, TEntityKey>> P_Indexer, IEqualityComparer<TEntityKey> P_Comparer)
		{
			if (P_Comparer == null)
				Store = new Dictionary<TEntityKey, TEntityClass>();
			else
				Store = new Dictionary<TEntityKey, TEntityClass>(P_Comparer);

			_Indexer = P_Indexer.Compile();
			_Lock = new ReaderWriterLockSlim();
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the unique key selector.
		/// </summary>
		/// <value>The selector.</value>
		private Func<TEntityClass, TEntityKey> _Indexer;

		private Dictionary<TEntityKey, TEntityClass> Store;

		#endregion

		#region Load & Reset

		/// <summary>
		/// Resets the mapper.
		/// </summary>
		public void Reset()
		{
			_Lock.WithWrite(_Reset);
		}

		private void _Reset()
		{
			Store.Clear();
		}

		/// <summary>
		/// Determines whether the specified key is loaded in mapper.
		/// </summary>
		/// <param name="P_Key">The key</param>
		/// <returns>
		/// 	<c>true</c> if the specified key is loaded; otherwise, <c>false</c>.
		/// </returns>
		public bool IsLoaded(object P_Key)
		{
			return _Lock.WithWrite(() => _IsLoaded((TEntityKey)P_Key));
		}

		private bool _IsLoaded(TEntityKey P_Key)
		{
			return Store.ContainsKey(P_Key);
		}

		public void Invalidate(object P_Key)
		{
			_Lock.WithWrite(() => _Invalidate((TEntityKey)P_Key));
		}

		private void _Invalidate(TEntityKey P_Key)
		{
			Store.Remove(P_Key);
		}

		public void MarkAsLoaded(object P_Key)
		{
			// do nothing
		}

		#endregion

		#region Getters
		/// <summary>
		/// Computes the index.
		/// </summary>
		/// <param name="P_Entity">The entity.</param>
		/// <returns></returns>
		public object ComputeIndex(TEntityClass P_Entity)
		{
			return _Indexer(P_Entity);
		}

		/// <summary>
		/// Gets the entity for the specified key.
		/// </summary>
		/// <param name="P_Key">The key</param>
		/// <returns></returns>
		public TEntityClass Get(TEntityKey P_Key)
		{
			return _Lock.WithRead(() => _Get(P_Key));
		}

		private TEntityClass _Get(TEntityKey P_Key)
		{
			TEntityClass L_Buffer = default(TEntityClass);

			if (Store.ContainsKey(P_Key))
				L_Buffer = Store[P_Key];
			return L_Buffer;
		}

		/// <summary>
		/// Gets the entities corresponding to the specified value.
		/// </summary>
		/// <param name="P_Key">The key</param>
		/// <returns></returns>
		public IEnumerable<TEntityClass> GetAllByKey(object P_Key)
		{
			return _Lock.WithRead(() => _GetAllByKey((TEntityKey)P_Key));
		}

		private IEnumerable<TEntityClass> _GetAllByKey(TEntityKey P_Key)
		{
			List<TEntityClass> L_Return = new List<TEntityClass>();

			TEntityClass L_Item = _Get(P_Key);

			if (L_Item != null)
				L_Return.Add(L_Item);
			return L_Return;
		}

		public bool IsInvalidated(object P_Key)
		{
			return _Lock.WithWrite(() => _IsInvalidated((TEntityKey)P_Key));
		}

		private bool _IsInvalidated(TEntityKey P_Key)
		{
			return Store.ContainsKey(P_Key);
		}
		#endregion

		#region Registering & Unregistering
		/// <summary>
		/// Registers an entity into mapper
		/// </summary>
		/// <param name="P_Entity"></param>
		private bool _Register(TEntityClass P_Entity)
		{
			TEntityKey L_EntityHash = _Indexer(P_Entity);
			bool L_Return = !Store.ContainsKey(L_EntityHash);
			Store[L_EntityHash] = P_Entity;

			return L_Return;
		}

		public bool Register(TEntityClass P_Entity)
		{
			return _Lock.WithWrite(() => _Register(P_Entity));
		}

		public void Remove(TEntityClass P_Entity)
		{
			_Lock.WithWrite(() => _Remove(P_Entity));
		}

		private void _Remove(TEntityClass P_Entity)
		{
			TEntityKey L_EntityHash = _Indexer(P_Entity);
			if (Store.ContainsKey(L_EntityHash))
				Store.Remove(L_EntityHash);
		}
		#endregion

		#region IDisposable
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="UniqueKeyMapper" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="UniqueKeyMapper" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~UniqueKeyMapper()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="UniqueKeyMapper" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				_Lock.Dispose();
				Store.Clear();
			}
		}
		#endregion

		#region IEnumerable
		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
		/// </returns>
		public IEnumerator<TEntityClass> GetEnumerator()
		{
			lock (_Lock)
			{
				foreach (TEntityClass L_Item in Store.Values)
				{
					yield return L_Item;
				}
			}
		}

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
		/// </returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		#endregion
	}
}
